//bange_scene.cpp
//Copyright (C) 2010-2011 Oscar (.teri) Triano

//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <cstring>
#include <bange_box.hpp>
#include <bange_scene.hpp>
#include <bange_layer.hpp>
#include <bange_layercolor.hpp>
#include <bange_layerobject.hpp>

using namespace std;

bange::scene::scene(float width, float height, int nlayers, lua_State *vm):bange::behavior(vm){
		this->width = width;
		this->height = height;
		this->nlayers = nlayers;
		this->space = cpSpaceNew();
		this->layers = new (nothrow) bange::layer *[nlayers];
		for(int i = 0; i < nlayers; i += 1){
			this->layers[i] = NULL;}
}

bool bange::scene::NewIndex(lua_State *vm){
	//userdata, key, data
	if(this->bange::behavior::NewIndex(vm)){
		return true;}
	char key[64] = {0};
	strncpy(key, lua_tostring(vm, 2), 64);
	if (strcmp(key, "gravityx") == 0 && lua_isnumber(vm, 3)){
		this->space->gravity.x = lua_tonumber(vm, 3);
		return true;
	}
	else if (strcmp(key, "gravityy") == 0 && lua_isnumber(vm, 3)){
		this->space->gravity.y = lua_tonumber(vm, 3);
		return true;
	}
	else if (strcmp(key, "iterations") == 0 && lua_isnumber(vm, 3)){
		this->space->iterations = lua_tonumber(vm, 3);
		return true;
	}
	else if (strcmp(key, "damping") == 0 && lua_isnumber(vm, 3)){
		this->space->damping = lua_tonumber(vm, 3);
		return true;
	}
	return false;
}

bool bange::scene::Index(lua_State *vm){
	//userdata, key
	if(this->bange::behavior::Index(vm)){
		return true;}
	if(lua_isnumber(vm, 2)){
		int ilayer = static_cast<int>(lua_tonumber(vm, 2))-1;
		if (ilayer < 0 || ilayer >= this->nlayers){
			cout << "bange::scene: Insert a index valid between 1 and scene.nlayers" << endl;
			lua_pushnil(vm);
			return true;
		}
		if (this->layers[ilayer] == NULL){
			lua_pushnil(vm);
			return true;
		}
		lua_pushvalue(vm, 1);
		int refparent = luaL_ref(vm, LUA_REGISTRYINDEX);
		bange::BuildProxy(refparent, dynamic_cast<bange::base *>(this->layers[ilayer]), vm);
		return true;
	}
	char key[64] = {0};
	strncpy(key, lua_tostring(vm, 2), 64);
	if (strcmp(key, "width") == 0){
		lua_pushnumber(vm, this->width);
		return true;
	}
	else if (strcmp(key, "height") == 0){
		lua_pushnumber(vm, this->height);
		return true;
	}
	else if (strcmp(key, "gravityx") == 0){
		lua_pushnumber(vm, this->space->gravity.x);
		return true;
	}
	else if (strcmp(key, "gravityy") == 0){
		lua_pushnumber(vm, this->space->gravity.y);
		return true;
	}
	else if (strcmp(key, "iterations") == 0){
		lua_pushnumber(vm, this->space->iterations);
		return true;
	}
	else if (strcmp(key, "damping") == 0){
		lua_pushnumber(vm, this->space->damping);
		return true;
	}
	lua_getfield(vm, LUA_REGISTRYINDEX, "bange::scene_method");
	lua_getfield(vm, -1, lua_tostring(vm, 2));
	if (lua_isfunction(vm, -1)){
		return true;}
	return false;
}

inline bange::layer **bange::scene::GetLayer(int ilayer){
	if (this->layers[ilayer] != NULL){
		return NULL;}
	return &this->layers[ilayer];
}

cpSpace *bange::scene::GetSpace(void){
	return this->space;
}

void bange::scene::CleanVM(lua_State *vm){
	for(int i = 0; i < this->nlayers; i += 1){
		if (this->layers[i] == NULL){
			continue;}
		this->layers[i]->CleanVM(vm);
	}
}

bange::scene::~scene(){
	for (int i = 0; i < this->nlayers; i += 1){
		delete this->layers[i];}
	delete [] this->layers;
	cpSpaceFree(this->space);
	cout << "bange::scene destructor for: " << this << endl;
}

void bange::scene::RegisterVM(lua_State *vm){
    luaL_Reg functions[] = {
    {"NewScene", bangescene_NewScene},
    {NULL, NULL}
    };
    luaL_register(vm, NULL, functions);
	luaL_Reg method[] = {
    {"ResizeActiveHash", bangescene_ResizeActiveHash},
    {"ResizeStaticHash", bangescene_ResizeStaticHash},
    {"SetLayer", bangescene_SetLayer},
    {NULL, NULL}
    };
    luaL_newmetatable(vm, "bange::scene_method");
    luaL_register(vm, NULL, method);
    lua_pop(vm, 1);
}

static int bangescene_NewScene(lua_State *vm){
    //nlayers, width, height
    int i = 0, width = 0, height = 0, nlayers = 0;
    //bange_colshape *colshape = NULL;
    if (!lua_isnumber(vm, 1)){
        printf("\nbange.NewScene: The function need as first argument the number of layers.");
        lua_pushnil(vm);
        return 1;}
    nlayers = lua_tonumber(vm, 1);
    width = lua_tonumber(vm, 2);
    height = lua_tonumber(vm, 3);
    if(nlayers <= 0){
		printf("\nNewScene: the n layers must be grater than 0.");
		lua_pushnil(vm);
		return 1;}
	if (width <= 0 || height <= 0){
		bange::box *box = bange::getbangebox(vm);
		width = box->GetWidth();
		height = box->GetHeight();
		}
	bange::scene *scene = new (nothrow) bange::scene(width, height, nlayers, vm);
	if (scene == NULL){
        printf("\nLua can't not assign memory for the userdata!");
        lua_pushnil(vm);
        return 1;}
    bange::BuildProxy(LUA_REFNIL, dynamic_cast<bange::base *>(scene), vm);
    /*
    scene->space = cpSpaceNew();
    scene->space->staticBody.data = malloc(sizeof(int));
    lua_pushvalue(vm, -1);
    *(int *)scene->space->staticBody.data = luaL_ref(vm, LUA_REGISTRYINDEX);
    lua_newtable(vm);
    scene->views = luaL_ref(vm, LUA_REGISTRYINDEX);
    scene->width = width;
    scene->height = height;
    scene->nlayers = nlayers;
    scene->call = LUA_REFNIL;
    scene->separate = LUA_REFNIL;
    scene->data = LUA_REFNIL;
    scene->layer = NULL;
    scene->layer = malloc(sizeof(bange_layer)*nlayers);
    scene->velocity = 1.0;
    if (scene->layer == NULL){
        printf("\nNo free memory for the layers!");
        lua_pushnil(vm);
        return 1;}
    */
    /*
    for(; i < scene->nlayers; i += 1){
        bangelayer_Init(&scene->layer[i], vm);
        scene->layer[i].space = scene->space;}
    cpSpaceAddCollisionHandler(scene->space, 2, 1, bangelayertilemap_Begin, bangelayertilemap_PreSolve, \
    bangelayertilemap_PostSolve, bangelayertilemap_Separate, (void *)vm);
    cpSpaceAddCollisionHandler(scene->space, 1, 1, \
    bangeobject_Begin, bangeobject_PreSolve, bangeobject_PostSolve, bangeobject_Separate, \
    (void *)vm);
     cpSpaceAddCollisionHandler(scene->space, 3, 1, \
    bangescene_Begin, bangescene_PreSolve, bangescene_PostSolve, bangescene_Separate, \
    (void *)vm);
    lua_createtable(vm, 4, 0);
    colshape = bangecolshape_NewSegment(vm, &scene->space->staticBody, (cpVect){0, 0}, (cpVect){0, scene->height}, 1.0);
    colshape->space = scene->space;
    colshape->shape->collision_type = 3;
    cpSpaceAddShape(scene->space, colshape->shape);
    lua_rawseti(vm, -2, 1);
    colshape = bangecolshape_NewSegment(vm, &scene->space->staticBody, (cpVect){0, 0}, (cpVect){scene->width, 0}, 1.0);
    colshape->space = scene->space;
    colshape->shape->collision_type = 3;
    cpSpaceAddShape(scene->space, colshape->shape);
    lua_rawseti(vm, -2, 2);
    colshape = bangecolshape_NewSegment(vm, &scene->space->staticBody, (cpVect){scene->width, 0}, (cpVect){scene->width, scene->height}, 1.0);
    colshape->space = scene->space;
    colshape->shape->collision_type = 3;
    cpSpaceAddShape(scene->space, colshape->shape);
    lua_rawseti(vm, -2, 3);
    colshape = bangecolshape_NewSegment(vm, &scene->space->staticBody, (cpVect){scene->width, scene->height}, (cpVect){0, scene->height}, 1.0);
    colshape->space = scene->space;
    colshape->shape->collision_type = 3;
    cpSpaceAddShape(scene->space, colshape->shape);
    lua_rawseti(vm, -2, 4);
    scene->border = luaL_ref(vm, LUA_REGISTRYINDEX);
    */
    return 1;
}

static int bangescene_ResizeStaticHash(lua_State *vm){
	//scene, dim, count
	bange::scene *scene = static_cast<bange::scene *>(bange::base::GetBaseFromProxy(1, vm));
	cpFloat dim = 100.f;
	int count = 1000;
	if (lua_isnumber(vm, 2)){
		dim = lua_tonumber(vm, 2);}
	if (lua_isnumber(vm, 3)){
		count = lua_tonumber(vm, 3);}
	cpSpaceResizeStaticHash(scene->space, dim, count);
	return 0;
}

static int bangescene_ResizeActiveHash(lua_State *vm){
	//scene, dim, count
	bange::scene *scene = static_cast<bange::scene *>(bange::base::GetBaseFromProxy(1, vm));
	cpFloat dim = 100.f;
	int count = 1000;
	if (lua_isnumber(vm, 2)){
		dim = lua_tonumber(vm, 2);}
	if (lua_isnumber(vm, 3)){
		count = lua_tonumber(vm, 3);}
	cpSpaceResizeActiveHash(scene->space, dim, count);
	return 0;
}

static int bangescene_SetLayer(lua_State *vm){
	//scene, ilayer, type, {parameters} (no table)
	int ilayer = lua_tonumber(vm, 2)-1;
	bange::scene *scene = static_cast<bange::scene *>(bange::base::GetBaseFromProxy(1, vm));
	if (ilayer < 0 || ilayer >= scene->nlayers){
		cout << "bange: scene:SetLayer: Must be a valid index." << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	bange::layer::layertype type = static_cast<bange::layer::layertype>(lua_tonumber(vm, 3));
	bange::layer **player = NULL;
	//player is 'p'ointer to layer
	player = scene->GetLayer(ilayer);
	if (player == NULL){
		cout << "bange: scene:SetLayer: The layer is already defined." << endl;
		lua_pushboolean(vm, 0);
		return 1;
	}
	switch(type){
		case bange::layer::Color:
			*player = static_cast<bange::layer *>(new bange::layercolor(640, 480, scene->space, vm));
		break;
		case bange::layer::Object:
			*player = static_cast<bange::layer *>(new bange::layerobject(scene->space, vm));
	}
	lua_pushboolean(vm, 1);
	return 1;
}
